package com.dk.hi.gui;

import org.newdawn.slick.Animation;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.SpriteSheet;
import org.newdawn.slick.geom.Polygon;

import com.dk.hi.core.Block;
import com.dk.hi.core.Direction;
import com.dk.hi.core.InputDTO;
import com.dk.hi.core.Settings;
import com.dk.hi.core.Speed;
import com.dk.hi.core.actor.Manner;
import com.dk.hi.core.helper.MapHelper;
import com.dk.hi.gui.comp.MapActorMenu;

/**
 * A MapActor is an entity on the BlockMap. Player can interact with then,
 * collide, and fight
 */
public class MapActor extends Block {
	/**
	 * Definition of a basic MapActor's bounds for collision
	 */
	private static final float MAP_ACTOR_BOUNDS[] = { 1, 15, 31, 15, 31, 31, 1, 31 };

	/**
	 * The sprite sheet path. This path is kept in order to recreate animations
	 * with another speed
	 */
	private final String spriteSheetPath;

	/**
	 * The actor's animation while looking up
	 */
	private Animation lookUp;
	private Animation lookUpWithManner;

	/**
	 * The actor's animation while looking down
	 */
	private Animation lookDown;
	private Animation lookDownWithManner;

	/**
	 * The actor's animation while looking left
	 */
	private Animation lookLeft;
	private Animation lookLeftWithManner;

	/**
	 * The actor's animation while looking right
	 */
	private Animation lookRight;
	private Animation lookRightWithManner;

	/**
	 * The current direction of the actor
	 */
	private Direction currentDirection;

	/**
	 * Animation Flag. True if the actor is currently moving on the map
	 */
	private boolean isMoving;

	/**
	 * Actor's X coordinate on the map
	 */
	private int x;

	/**
	 * Actor's Y coordinate on the map
	 */
	private int y;

	/**
	 * The distance within the actor may react to player's presence. This value
	 * is used to build the aggroBlock
	 */
	private float aggroDistance;

	/**
	 * Used to determine if the player is within the aggro range
	 */
	private Block aggroBlock;

	/**
	 * The manner of the actor
	 */
	private Manner manner;

	/**
	 * Actor's speed
	 */
	private Speed speed;

	/**
	 * True if the player's cursor is over th eactor
	 */
	private boolean hovered;

	/**
	 * The actor's action menu
	 */
	private MapActorMenu menu;

	public Manner getManner() {
		return this.manner;
	}

	public void setManner(Manner manner) throws SlickException {
		this.manner = manner;
		initAnimations(this.speed, manner);
	}

	/**
	 * @param spriteSheetPath
	 *            - the sprite sheet path
	 * @param direction
	 *            - the initial direction of the MapActor
	 * @param animationSpeed
	 *            - the Actor speed
	 * @throws SlickException
	 */
	public MapActor(String spriteSheetPath, Direction direction, Speed animationSpeed) throws SlickException {
		super(new Polygon(MAP_ACTOR_BOUNDS));

		// The direction
		this.currentDirection = direction;

		this.isMoving = false;

		// The animations
		this.spriteSheetPath = spriteSheetPath;
		this.speed = animationSpeed;
		initAnimations(animationSpeed, Manner.NONE);

		this.aggroDistance = 0;
		this.aggroBlock = null;
	}

	/**
	 * @return the actor's animation for it's current direction
	 */
	private Animation getCurrentAnimation() {
		switch (this.currentDirection) {
		case DOWN:
			return this.hovered ? this.lookDownWithManner : this.lookDown;
		case UP:
			return this.hovered ? this.lookUpWithManner : this.lookUp;
		case LEFT:
			return this.hovered ? this.lookLeftWithManner : this.lookLeft;
		case RIGHT:
			return this.hovered ? this.lookRightWithManner : this.lookRight;
		}
		return null;
	}

	/**
	 * @return the actor's current direction
	 */
	public Direction getCurrentDirection() {
		return this.currentDirection;
	}

	/**
	 * @return the actor's x coordinate
	 */
	public int getX() {
		return this.x;
	}

	/**
	 * @return the actor's y coordinate
	 */
	public int getY() {
		return this.y;
	}

	/**
	 * Initializes animations with the given speed
	 * 
	 * @param speed
	 *            - the speed
	 * @throws SlickException
	 */
	private void initAnimations(Speed speed, Manner manner) throws SlickException {
		// First we clean possible old animations
		this.lookUp = new Animation();
		this.lookUp.setAutoUpdate(false);
		this.lookDown = new Animation();
		this.lookDown.setAutoUpdate(false);
		this.lookLeft = new Animation();
		this.lookLeft.setAutoUpdate(false);
		this.lookRight = new Animation();
		this.lookRight.setAutoUpdate(false);

		this.lookUpWithManner = new Animation();
		this.lookUpWithManner.setAutoUpdate(false);
		this.lookDownWithManner = new Animation();
		this.lookDownWithManner.setAutoUpdate(false);
		this.lookLeftWithManner = new Animation();
		this.lookLeftWithManner.setAutoUpdate(false);
		this.lookRightWithManner = new Animation();
		this.lookRightWithManner.setAutoUpdate(false);

		SpriteSheet spriteSheet = new SpriteSheet(this.spriteSheetPath, 32, 32);
		BorderedImage temp = null;
		// Direction.DOWN
		for (int i = 0; i < 3; i++) {
			temp = new BorderedImage(spriteSheet.getSprite(i, 0));
			temp.setBorderSize(1);
			temp.setBorderColor(manner.getBorderColor());
			this.lookDownWithManner.addFrame(temp, speed.getValue());
			this.lookDown.addFrame(spriteSheet.getSprite(i, 0), speed.getValue());
		}

		// Direction.LEFT
		for (int i = 0; i < 3; i++) {
			temp = new BorderedImage(spriteSheet.getSprite(i, 1));
			temp.setBorderSize(1);
			temp.setBorderColor(manner.getBorderColor());
			this.lookLeftWithManner.addFrame(temp, speed.getValue());
			this.lookLeft.addFrame(spriteSheet.getSprite(i, 1), speed.getValue());
		}

		// Direction.RIGHT
		for (int i = 0; i < 3; i++) {
			temp = new BorderedImage(spriteSheet.getSprite(i, 2));
			temp.setBorderSize(1);
			temp.setBorderColor(manner.getBorderColor());
			this.lookRightWithManner.addFrame(temp, speed.getValue());
			this.lookRight.addFrame(spriteSheet.getSprite(i, 2), speed.getValue());
		}

		// Direction.UP
		for (int i = 0; i < 3; i++) {
			temp = new BorderedImage(spriteSheet.getSprite(i, 3));
			temp.setBorderSize(1);
			temp.setBorderColor(manner.getBorderColor());
			this.lookUpWithManner.addFrame(temp, speed.getValue());
			this.lookUp.addFrame(spriteSheet.getSprite(i, 3), speed.getValue());
		}
	}

	/**
	 * Moves the MapActor
	 * 
	 * @param dx
	 *            - x delta
	 * @param dy
	 *            - y delta
	 */
	public void moveOrigin(float dx, float dy) {
		this.x += dx;
		this.y += dy;
		moveBlock(dx, dy);

		// An actor may have no aggro block!
		if (this.aggroBlock != null) {
			this.aggroBlock.moveBlock(dx, dy);
		}

	}

	/**
	 * Renders the actor
	 * 
	 * @param g
	 *            - the canvas
	 * @param absolutePosition
	 * @throws SlickException
	 */
	public void render(Graphics g) throws SlickException {
		g.drawAnimation(getCurrentAnimation(), this.x, this.y);

		if (menu != null) {
			menu.render(g);
		}

		if (Settings.DEBUG_ENTITIES) {
			// Debug mode shows blocks for collision debug easing
			draw(g);
		}
	}

	/**
	 * Initializes the aggro distance and create the aggroBlock according to the
	 * distance
	 * 
	 * @param aggroDistance
	 *            - the aggro distance
	 */
	public void setAggroDistance(int aggroDistance) {
		this.aggroDistance = aggroDistance;
		float x1 = getBlockX() - aggroDistance;
		float y1 = getBlockY() - aggroDistance;
		float x2 = getBlockX() + getBlockWidth() + aggroDistance;
		float y2 = getBlockY() + getBlockHeight() + aggroDistance;
		Polygon aggroPoly = new Polygon(new float[] { x1, y1, x2, y1, x2, y2, x1, y2 });
		this.aggroBlock = new Block(aggroPoly);
	}

	/**
	 * Initializes the actor's current direction
	 * 
	 * @param direction
	 *            - the direction
	 */
	public void setDirection(Direction direction) {
		this.currentDirection = direction;
	}

	/**
	 * Update all animations setting autoUpdate to true or false
	 * 
	 * @param isMoving
	 *            -
	 */
	private void setMoving(boolean isMoving) {
		if (this.isMoving != isMoving) {
			this.lookDown.setAutoUpdate(isMoving);
			this.lookUp.setAutoUpdate(isMoving);
			this.lookLeft.setAutoUpdate(isMoving);
			this.lookRight.setAutoUpdate(isMoving);
			this.lookDownWithManner.setAutoUpdate(isMoving);
			this.lookUpWithManner.setAutoUpdate(isMoving);
			this.lookLeftWithManner.setAutoUpdate(isMoving);
			this.lookRightWithManner.setAutoUpdate(isMoving);
			this.isMoving = isMoving;
		}
		if (this.isMoving == false) {
			getCurrentAnimation().setCurrentFrame(1);
		}
	}

	/**
	 * Initializes the MapActor's position
	 * 
	 * @param x
	 *            - x coordinate
	 * @param y
	 *            - y coordinate
	 */
	public void setOrigin(int x, int y) {
		// The MapActor block doesn't start on the top left corner.
		super.setBlockOrigin(x, y + MAP_ACTOR_BOUNDS[1]);
		if (this.aggroBlock != null) {
			this.aggroBlock.setBlockOrigin(getBlockX() - this.aggroDistance, getBlockY() - this.aggroDistance);
		}
		this.x = x;
		this.y = y;
	}

	/**
	 * Initializes the actor's speed
	 * 
	 * @param speed
	 *            - the speed
	 * @throws SlickException
	 */
	public void setSpeed(Speed speed) throws SlickException {
		initAnimations(speed, this.manner);
	}

	/**
	 * Enable animation update
	 */
	public void startMoving() {
		setMoving(true);
	}

	/**
	 * Disable animation update
	 */
	public void stopMoving() {
		setMoving(false);
	}

	/**
	 * 
	 * @param input
	 *            - the game input
	 * @param player
	 *            - the player
	 * @throws SlickException
	 */
	public void update(InputDTO input, MapActor player) throws SlickException {
		// If the player is near the actor, an action may be done
		if (MapHelper.ACTION_CIRCLE.contains(this.x + Block.SIZE / 2, this.y + Block.SIZE / 2)) {
			if (this.isHovered() && input.isRightClick()) {
				if (this.menu == null) {
					// Let's display the menu
					this.menu = new MapActorMenu(this);
				} else {
					// The menu was already displayed but the player right
					// clicked again. The menu must
					// disapear
					this.menu = null;
				}
			}
		} else {
			this.menu = null;
		}
	}

	public void setHovered(boolean hovered) {
		this.hovered = hovered;
	}

	public boolean isHovered() {
		return this.hovered;
	}

	public void destroyMenu() {
		this.menu = null;
	}
}
